import {
	join,
	resolve,
	relative,
	dirname,
} from 'node:path';
import {
	fileURLToPath,
} from 'node:url';
import {
	stat,
} from 'node:fs/promises';
import lodash from 'lodash';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import {
	VueLoaderPlugin,
} from 'vue-loader';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import CopyPlugin from 'copy-webpack-plugin';
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
import {
	BundleAnalyzerPlugin,
} from 'webpack-bundle-analyzer';
import FileManagerPlugin from 'filemanager-webpack-plugin';
import ESLintPlugin from 'eslint-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import presetEnv from '@babel/preset-env';
import transformRuntime from '@babel/plugin-transform-runtime';
import autoprefixer from 'autoprefixer';
import PowerfulWebpackPlugin from './plugin.js';
import injectPolyfillsPlugin from '../util/injectPolyfillsPlugin.js';
import {
	extend,
	readJsonSync,
} from '../util/common.js';

const __dirname=dirname(fileURLToPath(import.meta.url));

export default async function({baseDir,packageJson,args,command,config}){
	const cache={};
	const htmlWebpackPlugins=[];
	let entry;
	const entryFiles=new Set();
	const isProd=process.env.NODE_ENV==='production';
	const isLib=command==='lib';
	const transpileDependencies=Array.from(new Set(config.babel.transpileDependencies)).map((dependency) => {
		if(typeof dependency==='string'){
			return join('node_modules',dependency);
		}
		return dependency;
	});
	//region 所有入口页面
	function getTemplateParametersFn(templateParameters, globalTemplateParameters){
		const isFn1 = typeof templateParameters === 'function';
		const isFn2 = typeof globalTemplateParameters === 'function';
		if(isFn1 || isFn2){
			return async function(compilation,assets,assetTags,options){
				return extend(
					{
						compilation:compilation,
						webpackConfig:compilation.options,
						htmlWebpackPlugin:{
							tags:assetTags,
							files:assets,
							options:options,
						},
					},
					isFn2
						?await globalTemplateParameters(compilation,assets,assetTags,options)
						:globalTemplateParameters,
					isFn1
						?await templateParameters(compilation,assets,assetTags,options)
						:templateParameters,
				);
			};
		}
		return extend({}, globalTemplateParameters, templateParameters);
	}
	if(isLib){
		entry=join(baseDir,config.libConfig.entry);
		entryFiles.add(entry);
		const exist=await stat(config.libConfig.html.template).then((stats) => {
			return stats.isFile();
		}).catch(() => false);
		htmlWebpackPlugins.push(
			new HtmlWebpackPlugin({
				title:config.libConfig.html.title||(config.libConfig.name+'库'),
				template:exist?config.libConfig.html.template:'template/index.html',
				templateContent:config.libConfig.html.templateContent??false,
				filename:config.libConfig.html.filename||'index.html',
				favicon:config.libConfig.html.favicon??config.htmlTemplate.favicon,
				minify:config.libConfig.html.minify??config.htmlTemplate.minify??true,
				templateParameters:getTemplateParametersFn(
					config.libConfig.html.templateParameters,
					config.htmlTemplate.templateParameters,
				),
				inject:config.libConfig.html.inject??config.htmlTemplate.inject,
				publicPath:config.libConfig.html.publicPath??'auto',
				base:config.libConfig.html.base??false,
				meta:config.libConfig.html.meta??{},
				scriptLoading:config.libConfig.html.scriptLoading,
			})
		);
	}else{
		entry={};
		const keys=Object.keys(config.pages);
		for(const key of keys){
			let value=config.pages[key];
			if(typeof value==='string'){
				value={
					entry:value,
					template:`template/${key}.html`,
					filename:`${key}.html`,
				};
			}
			entry[key]=join(baseDir,value.entry);
			entryFiles.add(entry[key]);
			if(value.html!==false){
				const exist=await stat(value.template).then((stats) => {
					return stats.isFile();
				}).catch(() => false);
				const option={
					title:value.title||packageJson.name,
					template:exist?value.template:'template/index.html',
					templateContent:value.templateContent??false,
					filename:value.filename||'index.html',
					favicon:value.favicon??config.htmlTemplate.favicon,
					minify:value.minify??config.htmlTemplate.minify??isProd,
					templateParameters:getTemplateParametersFn(
						value.templateParameters,
						config.htmlTemplate.templateParameters,
					),
					inject:value.inject??config.htmlTemplate.inject,
					publicPath:value.publicPath??'auto',
					base:value.base??false,
					meta:value.meta??{},
					excludeChunks:keys.filter((val) => val!==key),
				};
				htmlWebpackPlugins.push(new HtmlWebpackPlugin(option));
			}
		}
	}
	//endregion
	//region 静态资源拷贝
	function getToFn(to){
		return typeof to === 'function'
				?to
				:typeof to === 'string'
						?(() => to)
						:undefined;
	}
	function withContext(filter,transformer,from){
		const result={};
		const staticDir=resolve(baseDir,from);
		if(filter){
			result.filter=function(absolutePath){
				const relativePath=relative(staticDir,absolutePath);
				return filter(absolutePath,relativePath);
			};
		}
		if(transformer){
			result.transform=function(content,absolutePath){
				const relativePath=relative(staticDir,absolutePath);
				return transformer(content,absolutePath,relativePath);
			};
		}
		return result;
	}
	function getFilterFn(filters,globalFilter){
		if(typeof filters==='function'){
			if(globalFilter){
				return function(absolutePath,relativePath){
					return filters(absolutePath,relativePath,globalFilter);
				};
			}
			return filters;
		}else if(filters instanceof RegExp){
			return function(absolutePath,relativePath){
				return filters.test(relativePath);
			};
		}else if(Array.isArray(filters)&&filters.length){
			return async function(absolutePath,relativePath){
				for(const filter of filters){
					if(typeof filter==='function'){
						if(
							!(await filter(absolutePath,relativePath,globalFilter))
						){
							return false;
						}
					}else if(filter instanceof RegExp){
						if(!filter.test(relativePath)){
							return false;
						}
					}
				}
				return true;
			};
		}
		return undefined;
	}
	function getTransformerFn(transformers,globalTransformer){
		if(typeof transformers==='function'){
			if(globalTransformer){
				return function(content,absolutePath,relativePath){
					return transformers(content,absolutePath,relativePath,globalTransformer);
				}
			}
			return transformers;
		}else if(Array.isArray(transformers)&&transformers.length){
			return async function(content,absolutePath,relativePath){
				for(const transformer of transformers){
					content=await transformer(content,absolutePath,relativePath,globalTransformer);
				}
				return content;
			}
		}
		return undefined;
	}
	const copyPluginOption={
		patterns:[],
	};
	const globalFilter=getFilterFn(config.copy.filter);
	const globalTransformer=getTransformerFn(config.copy.transformer);
	if(typeof config.copy.dir==='string'){
		copyPluginOption.patterns.push({
			from:config.copy.dir,
			to:config.copy.to,
			toType:config.copy.toType,
			noErrorOnMissing:true,
			info:{minimized:!config.copy.minimize},
			...withContext(
				globalFilter,
				globalTransformer,
				config.copy.dir,
			),
		});
	}else if(config.copy.dir){
		const globalTo=getToFn(config.copy.to);
		for(const name of Object.keys(config.copy.dir)){
			const curOption=config.copy.dir[name];
			const {
				to,
				toType,
				priority,
				minimize,
				filter,
				transformer,
			}=curOption;
			copyPluginOption.patterns.push({
				from:name,
				to:'to' in curOption
					?(
						typeof to==='function'
							?function(pathData){
								return to(pathData,globalTo);
							}
							:to
					)
					:config.copy.to,
				toType:'toType' in curOption?toType:config.copy.toType,
				priority:priority||0,
				noErrorOnMissing:true,
				info:{minimized:!(minimize??config.copy.minimize)},
				...withContext(
					'filter' in curOption
							?getFilterFn(filter,globalFilter)
							:globalFilter,
					'transformer' in curOption
							?getTransformerFn(transformer,globalTransformer)
							:globalTransformer,
					name,
				),
			});
		}
	}
	//endregion
	return {
		mode:process.env.NODE_ENV,
		snapshot:{
			//解决serve模式下node_modules不能热更新的问题
			managedPaths:[],
		},
		context:baseDir,
		entry,
		//region 输出配置
		output:Object.assign(
			{
				publicPath:config.publicPath,
				path:join(baseDir,config.outputDir),
				clean:true,
			},
			isLib
				?{
					filename:'index.js',
					chunkFilename:`${config.assetsDir}/js/[name].[contenthash].js`,
					assetModuleFilename:`${config.assetsDir}/[hash][ext][query]`,
					chunkLoadingGlobal:`chunkLoading_lib_${lodash.camelCase(config.libConfig.name)}`,
					library:{
						name:lodash.camelCase(config.libConfig.name),
						type:'umd',
						export:config.libConfig.export,
					},
					asyncChunks:config.libConfig.asyncChunks,
				}
				:{
					filename:`${config.assetsDir}/js/[name].[contenthash].js`,
					chunkFilename:`${config.assetsDir}/js/chunk/[name].[contenthash].js`,
					assetModuleFilename:`${config.assetsDir}/[hash][ext][query]`,
					chunkLoadingGlobal:`chunkLoading_${lodash.camelCase(config.unique)}`,
				},
		),
		//endregion
		//region 模块
		module:{
			rules:[
				//region 加载vue
				{
					resolve:{
						fullySpecified:false,
					},
					test:/\.vue$/,
					resourceQuery:function(file){
						return !/^\?(resource|source|inline)$/.test(file);
					},
					use:(cache.vueLoaders = [
						{
							loader:'vue-loader',
							options:{
								compilerOptions:{
									whitespace:'condense',
								},
							},
						},
					]),
				},
				//endregion
				{
					resolve:{
						fullySpecified:false,
					},
					oneOf:[
						//region 资源查询
						{
							resourceQuery:/resource/,
							type:'asset/resource',
							generator:{
								filename:`${config.assetsDir}/[hash][ext]`,
							},
						},
						{
							resourceQuery:/source/,
							type:'asset/source',
						},
						{
							resourceQuery:/inline/,
							type:'asset/inline',
						},
						//endregion
						//region markdown
						{
							test:/\.md$/,
							use:[
								...cache.vueLoaders,
								{
									loader:resolve(__dirname, 'loaders/markdown.js'),
									options:config.markdown,
								},
							],
						},
						//endregion
						{
							test:/\.[mc]?js$/,
							rules:[
								//region 加载第三方库已有的 source-map
								{
									enforce:'pre',
									use:['source-map-loader'],
								},
								//endregion
								//region js
								{
									exclude:(file) => {
										return file.includes('node_modules')&&
											transpileDependencies.every((dependency) => {
												if(typeof dependency==='string'){
													return !file.includes(dependency);
												}else{
													return !dependency.test(file);
												}
											});
									},
									use:(cache.jsLoaders=[
										{
											loader:'babel-loader',
											options:{
												cacheDirectory:false,
												sourceType:'unambiguous',//兼容老代码转义
												presets:[
													[
														presetEnv,
														Object.assign(
															{
																modules:false,
																configPath:baseDir,
																bugfixes:true,
																corejs:readJsonSync(`${baseDir}/node_modules/core-js/package.json`).version,
																shippedProposals:true,
															},
															config.babel.presetOption,
															isLib
																?{
																	useBuiltIns:'entry',
																}
																:null
														),
													],
												],
												plugins:[
													[
														injectPolyfillsPlugin,
														{
															entryFiles,
															extraEntry:Array.from(new Set(config.babel.extraEntry)).map((entry) => {
																if(typeof entry==='string'){
																	return join('node_modules',entry);
																}
																return entry;
															}),
															polyfills:(isLib && config.libConfig.polyfillAll)
																||config.babel.polyfills===true
																||Array.from(new Set(config.babel.polyfills)),
															extraPolyfills:config.babel.extraPolyfills,
															vConsole:config.vConsole,
														}
													],
													[
														transformRuntime,
														{
															absoluteRuntime:false,
															corejs:false,
															regenerator:false,
															helpers:true,
															version:readJsonSync(`${baseDir}/node_modules/@babel/plugin-transform-runtime/package.json`).version,
														}
													]
												].concat(config.babel.plugins),
											}
										}
									]),
								},
								//endregion
							],
						},
						//region ts
						{
							test:/\.ts$/,
							use:[
								...cache.jsLoaders,
								{
									loader:'ts-loader',
									options:{
										appendTsSuffixTo:[/\.vue$/],
										transpileOnly:true,
										configFile:(cache.tsconfigPath=config.typescript.configFile||join(baseDir,'tsconfig.json')),
										compilerOptions:{
											sourceMap:isLib
												?config.libConfig.sourceMap!==false
												:(!isProd||config.buildConfig.sourceMap!==false),
										},
									}
								},
							],
						},
						//endregion
						//region 加载样式表
						{
							sideEffects:true,
							test:/\.css$/,
							oneOf:(function(){
								cache.cssSourceMap=isLib
									?config.libConfig.cssSourceMap
									:(!isProd||config.buildConfig.cssSourceMap)
								function getCssUse(modules){
									return [
										//生成模式下: 提取css到单独文件
										isLib||isProd?MiniCssExtractPlugin.loader:'vue-style-loader',
										{
											loader:'css-loader',
											options:{
												sourceMap:cache.cssSourceMap,
												importLoaders:3,
												modules:modules&&{
													localIdentName:isProd
														?'[hash:base64]'
														:'[local]-[hash:base64:8]',
													localIdentHashSalt:config.unique,
													namedExport:false,
													exportLocalsConvention:'as-is',
												},
											}
										},
										{
											loader:resolve(__dirname, 'loaders/css-preprocessor.js'),
											options:config.css,
										},
										{
											loader:'postcss-loader',
											options:{
												sourceMap:cache.cssSourceMap,
												postcssOptions:{
													plugins:[
														autoprefixer,
													]
												}
											}
										},
									];
								}
								return [
									{
										resourceQuery: /module/,
										use:(cache.cssModuleLoaders=getCssUse(true)),
									},
									{
										test:/\.module\.\w+$/,
										use:cache.cssModuleLoaders,
									},
									{
										use:(cache.cssLoaders=getCssUse(false)),
									},
								];
							})(),
						},
						{
							sideEffects:true,
							test:/\.scss$/,
							oneOf:[
								{
									resourceQuery: /module/,
									use:[
										...cache.cssModuleLoaders,
										(cache.scssLoaders={
											loader:'sass-loader',
											options:{
												api:'legacy',
												sourceMap:cache.cssSourceMap,
												sassOptions:{
													//style:'expanded',
													outputStyle:'expanded',
													silenceDeprecations:[
														'legacy-js-api',
													],
												},
											},
										})
									],
								},
								{
									test:/\.module\.\w+$/,
									use:[
										...cache.cssModuleLoaders,
										cache.scssLoaders,
									],
								},
								{
									use:[
										...cache.cssLoaders,
										cache.scssLoaders,
									],
								},
							],
						},
						//endregion
						//region 加载资源
						{
							test:/\.(png|jpe?g|gif|webp|svg)$/,
							type:'asset/resource',
							generator:{
								filename:`${config.assetsDir}/img/[hash][ext]`,
							},
						},
						{
							test:/\.(woff2?|eot|ttf|otf)$/,
							type:'asset/resource',
							generator:{
								filename:`${config.assetsDir}/font/[hash][ext]`,
							},
						},
						{
							test:/\.(mp4)$/,
							type:'asset/resource',
							generator:{
								filename:`${config.assetsDir}/video/[hash][ext]`,
							},
						},
						{
							test:/\.(mp3)$/,
							type:'asset/resource',
							generator:{
								filename:`${config.assetsDir}/audio/[hash][ext]`,
							},
						},
						//endregion
					],
				},
			],
		},
		//endregion
		//region 模块解析
		resolve:{
			alias:{
				'@':join(baseDir,'src'),
				vue$:'vue/dist/vue.runtime.esm-bundler.js',
				...config.alias,
			},
			extensions:[
				'.ts',
				'.js',
				'.mjs',
				'.cjs',
				'.vue',
				'.json',
				'.wasm',
			],
			mainFiles:[
				'index',
			],
			modules:[
				join(__dirname,'../node_modules'),
				join(baseDir,'node_modules'),
				'node_modules',
			],
		},
		//endregion
		//region loader模块解析
		resolveLoader:{
			modules:[
				join(__dirname,'../node_modules'),
				join(baseDir,'node_modules'),
				'node_modules',
			],
		},
		//endregion
		//region 优化
		optimization:{
			chunkIds:isProd?'deterministic':'named',
			concatenateModules:true,
			flagIncludedChunks:true,
			mangleExports:isProd?'deterministic':false,
			moduleIds:isProd?'deterministic':'named',
			providedExports:true,
			removeAvailableModules:true,
			runtimeChunk:'single',
			sideEffects:true,
			usedExports:true,
			minimizer:[
				//region css压缩
				new CssMinimizerPlugin({
					minimizerOptions:{
						preset:[
							'default',
							{
								discardComments:{removeAll:true},
							},
						],
					},
				}),
				//endregion
			],
		},
		//endregion
		//region 插件
		plugins:[
			new VueLoaderPlugin(),
			new webpack.DefinePlugin(config.definePlugin),
			new webpack.ProgressPlugin(),
			...(
				(function(){
					const {
						active,
						...options
					}=config.eslint;
					return active
						?[
							//region eslint
							new ESLintPlugin({
								context:baseDir,
								...options,
							}),
							//endregion
						]
						:[]
				})()
			),
			new CopyPlugin(copyPluginOption),
			new PowerfulWebpackPlugin(config),
			...htmlWebpackPlugins,
			...(
				config.typescript.active
					?[
						//region ts类型校验
						new ForkTsCheckerWebpackPlugin({
							typescript:{
								memoryLimit:4096,
								configFile:cache.tsconfigPath,
								mode:'readonly',
							}
						})
						//endregion
					]
					:[]
			),
			...(
				config.fileManager
					?[
						new FileManagerPlugin(config.fileManager),
					]
					:[]
			),
			...(
				command!=='serve'&&command!=='config'&&config.report.emit
					?[
						//region 包分析报告
						new BundleAnalyzerPlugin({
							logLevel:'warn',
							analyzerMode:config.report.type==='html'?'static':'disabled',
							reportFilename:config.report.filename+'.html',
							defaultSizes:'parsed',
							openAnalyzer:false,
							generateStatsFile:config.report.type==='json',
							statsFilename:config.report.filename+'.json',
							statsOptions:config.report.statsOptions,
						}),
						//endregion
					]
					:[]
			),
		],
		//endregion
	};
};